package com.jinmdz.fmis.api.api.service;

import com.jinmdz.fmis.api.api.model.common.IdData;
import com.jinmdz.fmis.api.api.model.common.IdListData;
import com.jinmdz.fmis.api.api.model.common.IdVersionData;
import com.jinmdz.fmis.api.api.model.funeral.BusinessCodeData;
import com.jinmdz.fmis.api.api.model.funeral.agreement.ServiceAgreementChargeItem;
import com.jinmdz.fmis.api.api.model.funeral.negotiation.NegotiationAgentItem;
import com.jinmdz.fmis.api.api.model.funeral.negotiation.NegotiationDeadItem;
import com.jinmdz.fmis.api.api.model.funeral.negotiation.NegotiationMemberItem;
import com.jinmdz.fmis.api.api.model.settlement.*;
import com.jinmdz.fmis.api.base.BaseService;
import com.jinmdz.fmis.api.constant.MessageConst;
import com.jinmdz.fmis.api.model.config.dictionary.BusinessLogCode;
import com.jinmdz.fmis.api.model.config.dictionary.DictionaryCode;
import com.jinmdz.fmis.api.model.config.dictionary.OperationLogCode;
import com.jinmdz.fmis.api.model.system.UserItem;
import com.jinmdz.fmis.api.wrapper.ChargeWrapper;
import com.jinmdz.fmis.api.wrapper.DictionaryWrapper;
import com.jinmdz.fmis.api.wrapper.OperationWrapper;
import com.jinmdz.fmis.common.ext.StringBuilderExt;
import com.jinmdz.fmis.common.util.BeanUtil;
import com.jinmdz.fmis.common.util.DataUtil;
import com.jinmdz.fmis.common.util.RenminbiUtil;
import com.jinmdz.fmis.core.base.BaseResult;
import com.jinmdz.fmis.core.constant.EDictCode;
import com.jinmdz.fmis.core.exception.ActionException;
import com.jinmdz.fmis.core.util.JacksonUtil;
import com.jinmdz.fmis.dao.dao.*;
import com.jinmdz.fmis.dao.model.charge.ChargeItem;
import com.jinmdz.fmis.dao.model.dictionary.DataDictionaryItem;
import com.jinmdz.fmis.dao.model.funeral.FamilyAgentItem;
import com.jinmdz.fmis.dao.model.funeral.FamilyMemberItem;
import com.jinmdz.fmis.dao.model.funeral.FuneralBusinessViewItem;
import com.jinmdz.fmis.dao.model.funeral.FuneralDeadItem;
import com.jinmdz.fmis.dao.model.settlement.ChargeSettleItem;
import com.jinmdz.fmis.dao.model.settlement.ChargeSettleViewItem;
import com.jinmdz.fmis.mapper.entity.ChargeItemEntity;
import com.jinmdz.fmis.mapper.entity.ChargeSettleEntity;
import com.jinmdz.fmis.mapper.entity.ChargeSettlePayEntity;
import com.jinmdz.fmis.mapper.entity.FuneralBusinessLogEntity;
import com.jinmdz.fmis.mapper.mapper.ChargeItemMapper;
import com.jinmdz.fmis.mapper.mapper.ChargeSettleMapper;
import com.jinmdz.fmis.mapper.mapper.ChargeSettlePayMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * ChargeSettlementService类
 *
 * @author LiCongLu
 * @date 2020-02-18 14:14
 */
@Service("chargeSettlementService")
public class ChargeSettlementService extends BaseService {

    @Resource
    private FuneralBusinessService funeralBusinessService;

    @Resource
    private FuneralBusinessDao funeralBusinessDao;

    @Resource
    private FuneralDeadDao funeralDeadDao;

    @Resource
    private FamilyMemberDao familyMemberDao;

    @Resource
    private FamilyAgentDao familyAgentDao;

    @Resource
    private DictionaryWrapper dictionaryWrapper;

    @Resource
    private ChargeItemDao chargeItemDao;

    @Resource
    private ChargeItemMapper chargeItemMapper;

    @Resource
    private ChargeSettleMapper chargeSettleMapper;

    @Resource
    private ChargeSettleDao chargeSettleDao;

    @Resource
    private ChargeSettlePayMapper chargeSettlePayMapper;

    @Resource
    private ChargeSettlePayDao chargeSettlePayDao;

    @Resource
    private OperationLogCode operationLogCode;

    @Resource
    private BusinessLogCode businessLogCode;

    @Resource
    private DictionaryCode dictionaryCode;

    @Resource
    private OperationWrapper operationWrapper;

    @Resource
    private ChargeWrapper chargeWrapper;

    @Resource
    ServiceNegotiationService serviceNegotiationService;

    /**
     * 根据funeral_dead表中businessCode加载逝者费用结算页面所有信息
     *
     * @param data 业务编码
     * @return
     * @author LiCongLu
     * @date 2020-02-18 14:20
     */
    public BaseResult<ChargeSettlementAllItem> loadChargeSettlementAll(BusinessCodeData data) {
        String businessCode = data.getBusinessCode();
        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            return failure(MessageConst.NO_FUNERAL_CODE);
        }

        // 创建响应实体
        ChargeSettlementAllItem loadItem = new ChargeSettlementAllItem();

        // 获取逝者信息
        FuneralDeadItem funeralDeadItem = funeralDeadDao.getFuneralDeadByBusinessCode(businessCode);
        if (DataUtil.noNullOrEmpty(funeralDeadItem)) {
            NegotiationDeadItem deadItem = BeanUtil.copy2Bean(funeralDeadItem, new NegotiationDeadItem());
            // 设置字典文本
            dictionaryWrapper.resetDataText(deadItem);
            loadItem.setFuneralDead(deadItem);
        }

        // 获取家属信息
        FamilyMemberItem familyMemberItem = familyMemberDao.getFamilyMemberByBusinessCode(businessCode);
        if (DataUtil.noNullOrEmpty(familyMemberItem)) {
            NegotiationMemberItem memberItem = BeanUtil.copy2Bean(familyMemberItem, new NegotiationMemberItem());
            loadItem.setFamilyMember(memberItem);
        }

        // 获取承办人信息
        FamilyAgentItem familyAgentItem = familyAgentDao.getFamilyAgentByBusinessCode(businessCode);
        if (DataUtil.noNullOrEmpty(familyAgentItem)) {
            NegotiationAgentItem agentItem = BeanUtil.copy2Bean(familyAgentItem, new NegotiationAgentItem());
            loadItem.setFamilyAgent(agentItem);
        }

        // 获取所有费用
        ArrayList<ChargeItem> chargeAllItems = chargeItemDao.listChargeItemByBusinessCode(businessCode);
        // 获取所有结算信息
        ArrayList<ChargeSettleViewItem> settleItems = chargeSettleDao.listChargeSettleViewByBusinessCode(businessCode, dictionaryCode.getInvoiceStateZuoFei());

        // 处理结算付款信息
        ArrayList<ChargeSettlementSettleItem> chargeSettles = BeanUtil.copy2List(settleItems, ChargeSettlementSettleItem.class);
        // 设置字典文本
        dictionaryWrapper.resetDataText(chargeSettles);
        loadItem.setChargeSettles(chargeSettles);

        // 关联费用结算信息
        ArrayList<ChargeSettlementChargeItem> chargeItems = new ArrayList<>();
        for (ChargeItem chargeAllItem : chargeAllItems) {
            ChargeSettlementChargeItem chargeItem = BeanUtil.copy2Bean(chargeAllItem, new ChargeSettlementChargeItem());
            // 设置付款信息
            resetChargeSettlementChargeItem(chargeItem, chargeSettles);
            chargeItems.add(chargeItem);
        }
        loadItem.setChargeItems(chargeItems);

        // 返回响应结果
        return successData(loadItem);
    }

    /**
     * 设置费用的结算信息
     *
     * @param chargeItem  费用
     * @param settleItems 结算集合
     * @return
     * @author LiCongLu
     * @date 2020-02-20 15:18
     */
    private void resetChargeSettlementChargeItem(ChargeSettlementChargeItem chargeItem, ArrayList<ChargeSettlementSettleItem> settleItems) {
        // 设置付款方式
        if (DataUtil.valid(chargeItem.getAsSettled())) {
            chargeItem.setAsSettledText("已结算");
            for (ChargeSettlementSettleItem settleItem : settleItems) {
                if (DataUtil.equals(chargeItem.getChargeSettleId(), settleItem.getId())) {
                    chargeItem.setSettleBatchNo(settleItem.getSettleBatchNo())
                            .setSettleTime(settleItem.getSettleTime())
                            .setPayType(settleItem.getPayType());
                    // 设置是否有打印发票
                    chargeItem.setAsInvoice(settleItem.getAsInvoice());
                    break;
                }
            }
        } else {
            chargeItem.setAsSettledText("未结算");
        }
    }

    /**
     * 根据费用id加载费用记录信息
     *
     * @param data 费用主键
     * @return
     * @author LiCongLu
     * @date 2020-02-18 15:07
     */
    public BaseResult<ChargeItemData> loadChargeItemWithId(IdData data) {
        // 获取费用
        ChargeItem loadItem = chargeItemDao.getChargeItemById(data.getId());
        if (DataUtil.isNull(loadItem) || DataUtil.isNull(loadItem.getId(), loadItem.getVersion())) {
            return failure("主键错误，不存在此费用信息");
        }

        // 转移返回值
        ChargeItemData loadData = BeanUtil.copy2Bean(loadItem, new ChargeItemData());

        // 返回响应结果
        return successData(loadData);
    }

    /**
     * 保存根据费用id加载的费用记录信息
     *
     * @param userItem 当前帐号
     * @param data     费用请求数据
     * @return
     * @author LiCongLu
     * @date 2020-02-18 15:44
     */
    @Transactional(rollbackFor = Exception.class)
    public BaseResult<ChargeItemData> saveChargeItemWithId(UserItem userItem, ChargeItemData data) throws ActionException {
        String businessCode = data.getBusinessCode();
        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            throw exception(MessageConst.NO_FUNERAL_CODE);
        }

        // 获取最新费用
        ChargeItemEntity chargeEntity = chargeItemMapper.getChargeItemById(data.getId());
        if (DataUtil.isNull(chargeEntity) || DataUtil.isNull(chargeEntity.getId(), chargeEntity.getVersion())) {
            return failure("主键错误，不存在此费用信息");
        }

        // 验证费用版本
        if (!DataUtil.equals(chargeEntity.getVersion(), data.getVersion())) {
            throw exception(versionError("费用信息"));
        }

        // 验证业务编码
        if (!chargeEntity.getBusinessCode().equals(businessCode)) {
            throw exception("请求业务编码与原业务编码不匹配");
        }

        // 验证结算
        if (DataUtil.valid(chargeEntity.getAsSettled())) {
            throw exception("此费用已经结算，无法进行修改");
        }

        // 验证是否退费
        if (DataUtil.valid(chargeEntity.getAsRefund())) {
            throw exception("此费用为退费关联费用，无法进行修改");
        }

        // 验证是否业务费用
        if (DataUtil.valid(chargeEntity.getAsBusiness())) {
            throw exception("此费用为业务费用，无法进行修改");
        }

        // 验证是否不可结算费用
        if (DataUtil.valid(chargeEntity.getAsUnsettled())) {
            throw exception("此费用为不可结算费用，无法进行修改");
        }

        // 设置新金额
        chargeEntity.setItemPrice(data.getItemPrice())
                .setItemNumber(data.getItemNumber())
                .setItemCharge(data.getItemCharge())
                .setDiscountCharge(data.getDiscountCharge())
                .setBenefitCharge(data.getBenefitCharge());

        // 处理消费时间
        if (DataUtil.isNull(chargeEntity.getChargeTime())) {
            chargeEntity.setChargeTime(nowDate());
        }
        if (!DataUtil.isNull(data.getChargeTime())) {
            chargeEntity.setChargeTime(data.getChargeTime());
        }

        // 判断比较实收金额与优惠惠民的金额
        chargeWrapper.checkChargeForEntity(chargeEntity);

        // 更新费用
        chargeItemMapper.updateChargeItem(chargeEntity);

        // 添加费用日志
        saveChargeItemLog(userItem, funeralItem, chargeEntity);

        // 返回费用信息
        return loadChargeItemWithId(new IdData(data.getId()));
    }

    /**
     * 添加费用修改记录
     *
     * @param userItem     当前帐号
     * @param funeralItem  当前业务
     * @param chargeEntity 费用信息
     * @return
     * @author LiCongLu
     * @date 2020-02-25 14:33
     */
    private void saveChargeItemLog(UserItem userItem, FuneralBusinessViewItem funeralItem, ChargeItemEntity chargeEntity) throws ActionException {

        // 添加业务日志及操作日志及
        String businessCode = funeralItem.getBusinessCode();
        Integer typeCode = businessLogCode.getChargeUpdate();
        FuneralBusinessLogEntity businessLogEntity = operationWrapper.businessLog(userItem, businessCode, typeCode);

        // 操作日志字符串
        StringBuilderExt builder = new StringBuilderExt();
        builder.format("费用信息修改，业务编码[{0}]，逝者姓名[{1}]，费用金额[{2}]，优惠金额[{3}]，惠民金额[{4}]，实收金额[{5}]，操作人员[{6}]"
                , businessCode, funeralItem.getDeadName()
                , DataUtil.getPlainString(chargeEntity.getItemCharge())
                , DataUtil.getPlainString(chargeEntity.getDiscountCharge())
                , DataUtil.getPlainString(chargeEntity.getBenefitCharge())
                , DataUtil.getPlainString(chargeEntity.getRealityCharge())
                , userItem.getFullName());

        // 添加日志
        operationWrapper.operationLog(userItem, businessLogEntity, operationLogCode.getChargeUpdate(), builder);
    }

    /**
     * 根据提供的费用项目id进行费用结算
     *
     * @param userItem 当前帐号
     * @param data     结算保存数据
     * @return
     * @author LiCongLu
     * @date 2020-02-19 11:00
     */
    @Transactional(rollbackFor = Exception.class)
    public BaseResult saveChargeSettleToSettleAccounts(UserItem userItem, ChargeAccountsData data) throws ActionException {
        ChargeAccountsSettleData settleData = data.getChargeSettle();
        String businessCode = settleData.getBusinessCode();
        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            throw exception(MessageConst.NO_FUNERAL_CODE);
        }

        // 判断费用集合是否存在
        if (DataUtil.invalid(data.getIds())) {
            throw exception("不存在要结算的费用");
        }

        // 判断付款集合是否存在
        if (DataUtil.invalid(data.getChargeSettlePay())) {
            throw exception("不存在付款信息");
        }

        // 物品
        ArrayList<IdVersionData> idList = data.getIds();
        // 记录费用主键
        ArrayList<Integer> chargeIds = new ArrayList<>();
        // 记录费用与版本号
        HashMap<Integer, Long> versionMap = new HashMap<>(16);
        for (IdVersionData item : idList) {
            if (!chargeIds.contains(item.getId())) {
                chargeIds.add(item.getId());
                versionMap.put(item.getId(), item.getVersion());
            }
        }

        // 去重
        chargeIds = DataUtil.repeatArrayList(chargeIds);

        // 验证Id是否重复
        if (idList.size() != chargeIds.size()) {
            throw exception("费用主键存在重复");
        }

        // 加载所要结算费用数据
        ArrayList<ChargeItem> chargeItems = chargeItemDao.listChargeItemByIds(businessCode, chargeIds);
        // 验证主键是否有效
        if (idList.size() != chargeItems.size()) {
            throw exception("费用明细已发生变化，请刷新后重新结算");
        }

        // 总应收金额
        BigDecimal receiptCharge = new BigDecimal(0);
        // 总优惠金额
        BigDecimal discountCharge = new BigDecimal(0);
        // 总惠民金额
        BigDecimal benefitCharge = new BigDecimal(0);

        // 总实际收入金额
        BigDecimal totalCharge = new BigDecimal(0);

        // 验证费用结算状态及费用数据版本
        for (ChargeItem chargeItem : chargeItems) {
            // 验证业务编码
            if (!businessCode.equals(chargeItem.getBusinessCode())) {
                throw exception(MessageConst.BUSINESS_CODE_ERROR);
            }

            // 验证是否结算
            if (DataUtil.valid(chargeItem.getAsSettled())) {
                throw exception("存在已经结算的费用");
            }

            // 判断是否不可结算
            if (DataUtil.valid(chargeItem.getAsUnsettled())) {
                throw exception("存在不可结算的费用");
            }

            // 验证数据版本号
            Long version = versionMap.get(chargeItem.getId());
            // 判断费用版本
            if (!DataUtil.equals(chargeItem.getVersion(), version)) {
                throw exception(versionError("费用"));
            }

            receiptCharge = receiptCharge.add(chargeItem.getItemCharge());
            discountCharge = discountCharge.add(chargeItem.getDiscountCharge());
            benefitCharge = benefitCharge.add(chargeItem.getBenefitCharge());

            // 累计实际收入金额
            totalCharge = totalCharge.add(chargeItem.getRealityCharge());
        }

        // 付款费用
        ArrayList<ChargeAccountsSettlePayData> payList = data.getChargeSettlePay();
        // 总实际付款金额
        BigDecimal payCharge = new BigDecimal(0);
        // 付款个数
        int paySize = payList.size();
        // 验证付款类型编码
        for (ChargeAccountsSettlePayData payData : payList) {
            DataDictionaryItem payType = dictionaryWrapper.getDataDictionaryItem(EDictCode.PAY_TYPE, payData.getPayTypeCode());
            if (DataUtil.isNull(payType)) {
                throw exception("存在错误付款类型");
            }

            // 判断付款金额
            if (payData.getPayCharge().compareTo(new BigDecimal(0)) <= 0) {
                throw exception("存在付款金额不大于零的情况");
            }

            // 判断是否挂账
            if (DataUtil.equals(payData.getPayTypeCode(), dictionaryCode.getPayTypeGuaZhang())) {
                if (paySize > 1) {
                    throw exception("挂账只能存在一条付款信息");
                }
            } else {
                // 当非挂账付款时，挂账单位为空
                payData.setAccountUnit(null);
            }

            // 累计付款收入金额
            payCharge = payCharge.add(payData.getPayCharge());
        }

        // 判断付款金额与结算总金额是否相等
        if (totalCharge.compareTo(payCharge) != 0) {
            throw exception("付款总金额与实际需要结算金额不相等");
        }

        // 判断付款金额与提交结算总金额是否相等
        if (totalCharge.compareTo(settleData.getSettleCharge()) != 0) {
            throw exception("付款总金额与提交结算金额不相等");
        }

        // 判断是否总额与实际收入总额的是否相等
        if (totalCharge.compareTo(receiptCharge.subtract(discountCharge).subtract(benefitCharge)) != 0) {
            throw exception("付款总金额与实际收入总额不相等");
        }

        // 判断结算时间
        if (DataUtil.isNull(settleData.getSettleTime())) {
            settleData.setSettleTime(nowDate());
        }

        // 保存费用结算
        ChargeSettleEntity settleEntity = getEntity(userItem, ChargeSettleEntity.class);
        BeanUtil.copy2Bean(settleData, settleEntity);
        settleEntity.setBusinessCode(businessCode)
                .setRandomCode(DataUtil.getUUID())
                // 默认为收费
                .setSettleTypeCode(dictionaryCode.getSettleTypeShouFei())
                .setSettleUserId(userItem.getId())
                // 生成结算批次编号
                .setSettleBatchNo(funeralBusinessService.getSettleBatchNoAsNew())
                .setReceiptCharge(receiptCharge)
                .setDiscountCharge(discountCharge)
                .setBenefitCharge(benefitCharge)
                .setSettleCharge(totalCharge)
                .setAsRefund(0)
                .setAsVerify(0);

        // 添加执行结算
        chargeSettleMapper.insertChargeSettle(settleEntity);
        // 判断结算结果
        if (DataUtil.invalid(settleEntity.getId())) {
            throw exception("保存结算费用失败");
        }

        // 保存结算付款信息
        for (ChargeAccountsSettlePayData payData : payList) {
            ChargeSettlePayEntity payEntity = getEntity(userItem, ChargeSettlePayEntity.class);
            payEntity.setBusinessCode(businessCode)
                    .setChargeSettleId(settleEntity.getId())
                    .setPayTypeCode(payData.getPayTypeCode())
                    .setPayCharge(payData.getPayCharge())
                    .setAccountUnit(payData.getAccountUnit())
                    .setPayTime(settleEntity.getSettleTime())
                    .setRandomCode(settleEntity.getRandomCode());
            // 添加结算付款信息
            chargeSettlePayMapper.insertChargeSettlePay(payEntity);
            // 判断结算付款信息
            if (DataUtil.invalid(settleEntity.getId())) {
                throw exception("保存费用结算付款失败");
            }
        }

        // 更新费用结算状态
        for (Map.Entry<Integer, Long> entry : versionMap.entrySet()) {
            Integer id = entry.getKey();
            Long version = entry.getValue();
            // 查询费用
            ChargeItemEntity chargeEntity = chargeItemMapper.getChargeItemById(id);
            // 再次验证费用数据版本
            if (!DataUtil.equals(chargeEntity.getVersion(), version)) {
                throw exception(versionError("费用"));
            }

            // 执行结算
            chargeEntity.setAsSettled(1)
                    .setChargeSettleId(settleEntity.getId())
                    .setModifiedUserId(userItem.getId());
            // 更新结算
            chargeItemMapper.updateChargeItem(chargeEntity);
        }

        // 添加业务及操作日志
        saveChargeSettleToSettleAccountsLog(userItem, funeralItem, data, settleEntity);

        return success("费用结算完成");
    }

    /**
     * 添加费用记录
     *
     * @param userItem     当前帐号
     * @param funeralItem  当前业务
     * @param data         请求数据
     * @param settleEntity 结算实体
     * @return
     * @author LiCongLu
     * @date 2020-02-25 20:20
     */
    private void saveChargeSettleToSettleAccountsLog(UserItem userItem, FuneralBusinessViewItem funeralItem, ChargeAccountsData data, ChargeSettleEntity settleEntity) throws ActionException {

        // 添加业务日志及操作日志
        String businessCode = funeralItem.getBusinessCode();
        Integer typeCode = businessLogCode.getChargeSettle();
        FuneralBusinessLogEntity businessLogEntity = operationWrapper.businessLog(userItem, businessCode, typeCode);

        // 操作日志字符串
        StringBuilderExt builder = new StringBuilderExt();
        builder.format("费用结算，业务编码[{0}]，逝者姓名[{1}]，结算总额[{2}]，操作人员[{3}]"
                , businessCode, funeralItem.getDeadName(), DataUtil.getPlainString(settleEntity.getSettleCharge()), userItem.getFullName());

        // 添加日志
        operationWrapper.operationLog(userItem, businessLogEntity, operationLogCode.getChargeSettle(), builder.toString()
                , JacksonUtil.obj2Json(data), null, JacksonUtil.obj2Json(settleEntity));
    }

    /**
     * 撤销结算，即删除结算相关信息
     *
     * @param userItem 当前帐号
     * @param data     主键和版本号
     * @return
     * @author LiCongLu
     * @date 2020-02-20 11:46
     */
    @Transactional(rollbackFor = Exception.class)
    public BaseResult<ChargeSettlementAllItem> saveChargeSettleToCancellationSettlement(UserItem userItem, IdListData data) throws ActionException {
        String businessCode = data.getBusinessCode();
        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            throw exception(MessageConst.NO_FUNERAL_CODE);
        }

        // 判断是否存在主键
        if (DataUtil.invalid(data.getIds())) {
            throw exception("不存在结算主键");
        }

        // 结算主键处理
        ArrayList<IdVersionData> idList = data.getIds();

        // 验证主键是否重复
        if (idList.size() != getRepeatIdSize(idList)) {
            throw exception("结算主键存在重复");
        }

        //  记录结算费用
        ArrayList<ChargeSettleViewItem> chargeSettleItems = new ArrayList<>();
        // 总结算金额
        BigDecimal totalCharge = new BigDecimal(0);

        // 开始遍历结算
        for (IdVersionData idData : data.getIds()) {
            ChargeSettleViewItem settleItem = chargeSettleDao.getChargeSettleViewById(idData.getId());
            if (DataUtil.isNull(settleItem) || DataUtil.isNull(settleItem.getId(), settleItem.getVersion())) {
                throw exception("存在错误结算主键");
            }

            // 验证数据版本号
            if (!DataUtil.equals(settleItem.getVersion(), idData.getVersion())) {
                throw exception(versionError("结算主键"));
            }

            // 验证业务编码
            if (!businessCode.equals(settleItem.getBusinessCode())) {
                throw exception(MessageConst.BUSINESS_CODE_ERROR);
            }

            // 验证是否日结审核
            if (DataUtil.valid(settleItem.getAsVerify())) {
                throw exception("存在已审核结算");
            }

            // 验证是否有效发票
            if (DataUtil.valid(settleItem.getAsInvoice())) {
                throw exception("存在已打印发票结算");
            }

            // 验证是否结算退费
            checkChargeSettleAsRefund(settleItem);

            // 撤销费用结算状态
            chargeItemDao.updateForCancellationSettlement(businessCode, settleItem.getId(), userItem.getId());

            // 删除结算付款记录
            chargeSettlePayDao.deletedForCancellationSettlement(businessCode, settleItem.getId(), userItem.getId());

            // 删除结算信息
            chargeSettleMapper.deletedForChargeSettle(settleItem.getId(), userItem.getId(), settleItem.getVersion());

            // 记录结算金额
            totalCharge = totalCharge.add(settleItem.getSettleCharge());
            // 记录结算费用
            chargeSettleItems.add(settleItem);
        }

        // 添加业务及操作日志
        saveChargeSettleToCancellationSettlementLog(userItem, funeralItem, data, chargeSettleItems, totalCharge);

        // 撤销完成，重新加载当前结算界面
        return loadChargeSettlementAll(new BusinessCodeData().setBusinessCode(businessCode));
    }

    /**
     * 添加结算取消记录
     *
     * @param userItem          当前帐号
     * @param funeralItem       当前业务
     * @param data              请求数据
     * @param chargeSettleItems 结算费用信息
     * @param totalCharge       结算总额
     * @return
     * @author LiCongLu
     * @date 2020-02-25 20:20
     */
    private void saveChargeSettleToCancellationSettlementLog(UserItem userItem, FuneralBusinessViewItem funeralItem, IdListData data, ArrayList<ChargeSettleViewItem> chargeSettleItems, BigDecimal totalCharge) throws ActionException {

        // 添加业务日志及操作日志
        String businessCode = funeralItem.getBusinessCode();
        Integer typeCode = businessLogCode.getChargeSettleDelete();
        FuneralBusinessLogEntity businessLogEntity = operationWrapper.businessLog(userItem, businessCode, typeCode);

        // 操作日志字符串
        StringBuilderExt builder = new StringBuilderExt();
        builder.format("费用结算取消，业务编码[{0}]，逝者姓名[{1}]，取消结算总额[{2}]，操作人员[{3}]"
                , businessCode, funeralItem.getDeadName(), DataUtil.getPlainString(totalCharge), userItem.getFullName());

        // 添加日志
        operationWrapper.operationLog(userItem, businessLogEntity, operationLogCode.getChargeSettle(), builder.toString()
                , JacksonUtil.obj2Json(data), JacksonUtil.obj2Json(chargeSettleItems), null);
    }

    /**
     * 判断是否退费结算
     *
     * @param settleItem 结算信息
     * @return
     * @author LiCongLu
     * @date 2020-02-20 17:45
     */
    private void checkChargeSettleAsRefund(ChargeSettleItem settleItem) throws ActionException {

        // 验证是否退费
        if (DataUtil.valid(settleItem.getAsRefund())) {
            throw exception("存在已退费的结算");
        }

        // 判断结算金额是否是负数
        if (settleItem.getSettleCharge().compareTo(new BigDecimal(0)) < 0) {
            throw exception("存在结算金额为负值的结算主键");
        }

        // 判断结算类型
        if (DataUtil.equals(settleItem.getSettleTypeCode(), dictionaryCode.getSettleTypeTuiFei())) {
            throw exception("存在结算类型为退费的费用主键");
        }
    }

    /**
     * 更改结算记录收据号码信息
     *
     * @param userItem 当前帐号
     * @param data     发票数据
     * @return
     * @author LiCongLu
     * @date 2020-02-20 18:31
     */
    public BaseResult saveChargeSettleChangeReceipt(UserItem userItem, ChangeReceiptData data) throws ActionException {
        String businessCode = data.getBusinessCode();
        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            throw exception(MessageConst.NO_FUNERAL_CODE);
        }

        ChargeSettleViewItem settleItem = chargeSettleDao.getChargeSettleViewById(data.getId());
        if (DataUtil.isNull(settleItem) || DataUtil.isNull(settleItem.getId(), settleItem.getVersion())) {
            throw exception("不存在此结算信息");
        }

        // 验证数据版本号
        if (!DataUtil.equals(settleItem.getVersion(), data.getVersion())) {
            throw exception(versionError("结算主键"));
        }

        // 验证业务编码
        if (!businessCode.equals(settleItem.getBusinessCode())) {
            throw exception(MessageConst.BUSINESS_CODE_ERROR);
        }

        // 验证是否日结审核
        if (DataUtil.valid(settleItem.getAsVerify())) {
            throw exception("此结算已审核");
        }

        // 验证是否结算退费
        checkChargeSettleAsRefund(settleItem);

        // 获取结算数据
        ChargeSettleEntity settleEntity = chargeSettleMapper.getChargeSettleById(settleItem.getId());
        settleEntity.setReceiptNo(data.getReceiptNo())
                .setRemark(settleEntity.getRemark() + data.getRemark())
                .setModifiedUserId(userItem.getId());
        // 更新结算发票信息
        chargeSettleMapper.updateChargeSettle(settleEntity);

        // 添加业务及操作日志
        saveChargeSettleChangeReceiptLog(userItem, funeralItem, settleItem, settleEntity);

        return success("更新发票信息完成");
    }

    /**
     * 添加修改发票日志记录
     *
     * @param userItem     当前帐号
     * @param funeralItem  当前业务
     * @param settleItem   旧结算数据
     * @param settleEntity 结算费用
     * @return
     * @author LiCongLu
     * @date 2020-02-25 20:37
     */
    private void saveChargeSettleChangeReceiptLog(UserItem userItem, FuneralBusinessViewItem funeralItem, ChargeSettleViewItem settleItem, ChargeSettleEntity settleEntity) throws ActionException {

        // 添加业务日志及操作日志
        String businessCode = funeralItem.getBusinessCode();
        Integer typeCode = businessLogCode.getChargeSettleReceipt();
        FuneralBusinessLogEntity businessLogEntity = operationWrapper.businessLog(userItem, businessCode, typeCode);

        // 操作日志字符串
        StringBuilderExt builder = new StringBuilderExt();
        builder.format("费用结算修收据信息，业务编码[{0}]，逝者姓名[{1}]，结算总额[{2}]，操作人员[{3}]"
                , businessCode, funeralItem.getDeadName(), DataUtil.getPlainString(settleEntity.getSettleCharge()), userItem.getFullName());

        // 添加日志
        operationWrapper.operationLog(userItem, businessLogEntity, operationLogCode.getChargeSettle(), builder.toString()
                , null, JacksonUtil.obj2Json(settleItem), JacksonUtil.obj2Json(settleEntity));
    }

    /**
     * 打印费用明细接口
     *
     * @param userItem 当前帐号
     * @param data     费用项目ID信息
     * @return
     * @author ZhangLiRui
     * @date 2020/4/15 14:06
     */
    public BaseResult<ChargeDetailsItem> loadPrintChargeDetailsWithId(UserItem userItem, IdListData data) {
        String businessCode = data.getBusinessCode();

        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            return failure(MessageConst.NO_FUNERAL_CODE);
        }

        // 判断是否存在主键
        if (DataUtil.invalid(data.getIds())) {
            return failure("不存在费用明细主键");
        }

        // 费用项目主键处理
        ArrayList<IdVersionData> idList = data.getIds();
        // 记录费用主键
        ArrayList<Integer> ids = new ArrayList<Integer>();
        // 记录费用与版本号
        HashMap<Integer, Long> versionMap = new HashMap<>(16);
        for (IdVersionData idVersionData : idList) {
            if (!idList.contains(idVersionData.getId())) {
                ids.add(idVersionData.getId());
                versionMap.put(idVersionData.getId(), idVersionData.getVersion());
            }
        }

        // 验证主键是否重复
        if (idList.size() != ids.size()) {
            return failure("费用项目主键存在重复");
        }

        // 创建响应实体
        ChargeDetailsItem loadItem = new ChargeDetailsItem();

        // 获取逝者信息
        FuneralDeadItem funeralDeadItem = funeralDeadDao.getFuneralDeadByBusinessCode(businessCode);
        loadItem = BeanUtil.copy2Bean(funeralDeadItem, loadItem);

        // 获取打印的所有费用明细数据
        ArrayList<ChargeItem> chargeAllItems = chargeItemDao.listChargeItemByIds(businessCode, ids);

        // 判断费用主键是否都存在
        if (idList.size() != chargeAllItems.size()) {
            return failure("费用项目主键错误");
        }

        // 服务费用列表
        ArrayList<ServiceAgreementChargeItem> chargeItems = new ArrayList<>();

        // 记录结算主键
        ArrayList<Integer> settleIds = new ArrayList<Integer>();

        // 遍历费用，进行解析
        for (ChargeItem chargeItem : chargeAllItems) {
            // 验证业务编码
            if (!businessCode.equals(chargeItem.getBusinessCode())) {
                throw exception(MessageConst.BUSINESS_CODE_ERROR);
            }

            // 验证数据版本号
            Long version = versionMap.get(chargeItem.getId());
            // 判断费用版本
            if (!DataUtil.equals(chargeItem.getVersion(), version)) {
                return failure(versionError("费用项目"));
            }

            // 记录结算主键
            if (DataUtil.valid(chargeItem.getAsSettled())) {
                settleIds.add(chargeItem.getChargeSettleId());
            }

            // 服务费用列表
            chargeItems.add(new ServiceAgreementChargeItem()
                    .setServiceItemId(chargeItem.getServiceItemId())
                    .setItemName(chargeItem.getItemName())
                    .setItemCharge(chargeItem.getItemCharge()));
            // 费用金额
            loadItem.setItemTotalCharge(DataUtil.add(loadItem.getItemTotalCharge(), chargeItem.getItemCharge()));

            // 优惠金额
            if (DataUtil.greaterAllZero(chargeItem.getDiscountCharge())) {
                loadItem.setDiscountTotalCharge(DataUtil.add(loadItem.getDiscountTotalCharge(), chargeItem.getDiscountCharge()));
            }

            // 惠民金额
            if (DataUtil.greaterAllZero(chargeItem.getBenefitCharge())) {
                loadItem.setBenefitTotalCharge(DataUtil.add(loadItem.getBenefitTotalCharge(), chargeItem.getBenefitCharge()));
            }

            // 设置一个收费日期
            loadItem.setChargeDate(chargeItem.getChargeTime());

            // 实收金额
            loadItem.setRealityTotalCharge(DataUtil.add(loadItem.getRealityTotalCharge(), chargeItem.getRealityCharge()));
        }

        // 显示大写
        loadItem.setRealityTotalChargeText(RenminbiUtil.toRmb(loadItem.getRealityTotalCharge()));

        // 设置合并项目后的集合
        loadItem.setChargeItems(serviceNegotiationService.repeatSumChargeItems(chargeItems));

        // 如果存在结算主键，则获取付款方式
        if (DataUtil.valid(settleIds)) {
            //根据结算id集合获得结算数据付款方式
            String payTypes = chargeSettleDao.getPayTypeBySettleIds(businessCode, settleIds);
            loadItem.setPayType(payTypes);
        }

        // 设置制单信息
        loadItem.setPrintDate(nowDate());
        loadItem.setUserFullName(userItem.getFullName());

        // 返回响应结果
        return successData(loadItem);
    }

    /**
     * 打印结算明细
     *
     * @param userItem 当前帐号
     * @param data     结算ID信息
     * @return
     * @author ZhangLiRui
     * @date 2020/4/16 16:35
     */
    public BaseResult<ChargeSettleDetailsItem> loadPrintChargeSettleDetailsWithId(UserItem userItem, IdListData data) {
        String businessCode = data.getBusinessCode();

        // 通过业务编码获取殡葬信息
        FuneralBusinessViewItem funeralItem = funeralBusinessDao.getFuneralBusinessViewByBusinessCode(businessCode);
        if (DataUtil.isNull(funeralItem) || DataUtil.isNull(funeralItem.getId(), funeralItem.getVersion())) {
            return failure(MessageConst.NO_FUNERAL_CODE);
        }

        // 判断是否存在主键
        if (DataUtil.invalid(data.getIds())) {
            throw exception("不存在结算主键");
        }

        // 结算主键处理
        ArrayList<IdVersionData> idList = data.getIds();

        // 获取去重主键
        ArrayList<Integer> repeatIdList = getRepeatIdList(idList);

        // 验证主键是否重复
        if (idList.size() != repeatIdList.size()) {
            throw exception("结算主键存在重复");
        }

        // 创建响应实体
        ChargeSettleDetailsItem loadItem = new ChargeSettleDetailsItem();

        // 获取逝者信息
        FuneralDeadItem funeralDeadItem = funeralDeadDao.getFuneralDeadByBusinessCode(businessCode);
        loadItem = BeanUtil.copy2Bean(funeralDeadItem, loadItem);

        // 初始化字典文本
        dictionaryWrapper.resetDataText(loadItem);

        // 开始遍历结算
        for (IdVersionData idData : data.getIds()) {
            ChargeSettleViewItem settleItem = chargeSettleDao.getChargeSettleViewById(idData.getId());
            if (DataUtil.isNull(settleItem) || DataUtil.isNull(settleItem.getId(), settleItem.getVersion())) {
                throw exception("存在错误结算主键");
            }

            // 验证数据版本号
            if (!DataUtil.equals(settleItem.getVersion(), idData.getVersion())) {
                throw exception(versionError("结算主键"));
            }

            // 验证业务编码
            if (!businessCode.equals(settleItem.getBusinessCode())) {
                throw exception(MessageConst.BUSINESS_CODE_ERROR);
            }

        }

        // 根据已结算ID集合获取打印的所有费用明细数据
        ArrayList<ChargeItem> chargeAllItems = chargeItemDao.listChargeItemBySettleIds(businessCode, repeatIdList);

        // 服务费用列表
        ArrayList<ServiceAgreementChargeItem> chargeItems = new ArrayList<>();

        // 优惠费用信息
        ArrayList<ServiceAgreementChargeItem> discountChargeItems = new ArrayList<>();

        // 惠民费用信息
        ArrayList<ServiceAgreementChargeItem> benefitChargeItems = new ArrayList<>();

        // 遍历费用，进行解析
        for (ChargeItem chargeItem : chargeAllItems) {

            // 服务费用列表
            chargeItems.add(new ServiceAgreementChargeItem()
                    .setServiceItemId(chargeItem.getServiceItemId())
                    .setItemName(chargeItem.getItemName())
                    .setItemCharge(chargeItem.getItemCharge()));
            // 费用金额
            loadItem.setItemTotalCharge(DataUtil.add(loadItem.getItemTotalCharge(), chargeItem.getItemCharge()));

            // 优惠金额
            if (DataUtil.greaterAllZero(chargeItem.getDiscountCharge())) {
                discountChargeItems.add(new ServiceAgreementChargeItem()
                        .setServiceItemId(chargeItem.getServiceItemId())
                        .setItemName(chargeItem.getItemName())
                        .setItemCharge(chargeItem.getDiscountCharge()));

                loadItem.setDiscountTotalCharge(DataUtil.add(loadItem.getDiscountTotalCharge(), chargeItem.getDiscountCharge()));
            }

            // 惠民金额
            if (DataUtil.greaterAllZero(chargeItem.getBenefitCharge())) {
                benefitChargeItems.add(new ServiceAgreementChargeItem()
                        .setServiceItemId(chargeItem.getServiceItemId())
                        .setItemName(chargeItem.getItemName())
                        .setItemCharge(chargeItem.getBenefitCharge()));
                loadItem.setBenefitTotalCharge(DataUtil.add(loadItem.getBenefitTotalCharge(), chargeItem.getBenefitCharge()));
            }

            // 设置一个收费日期
            loadItem.setChargeDate(chargeItem.getChargeTime());

            // 实收金额
            loadItem.setRealityTotalCharge(DataUtil.add(loadItem.getRealityTotalCharge(), chargeItem.getRealityCharge()));
        }

        // 显示大写
        loadItem.setRealityTotalChargeText(RenminbiUtil.toRmb(loadItem.getRealityTotalCharge()));

        // 设置合并项目后的集合
        loadItem.setChargeItems(serviceNegotiationService.repeatSumChargeItems(chargeItems));
        loadItem.setDiscountChargeItems(serviceNegotiationService.repeatSumChargeItems(discountChargeItems));
        loadItem.setBenefitChargeItems(serviceNegotiationService.repeatSumChargeItems(benefitChargeItems));

        //根据结算id集合获得结算付款方式
        String payTypes = chargeSettleDao.getPayTypeBySettleIds(businessCode, repeatIdList);
        loadItem.setPayType(payTypes);

        // 设置制单信息
        loadItem.setPrintDate(nowDate());
        loadItem.setUserFullName(userItem.getFullName());

        // 返回响应结果
        return successData(loadItem);
    }

}
